<?php
namespace App\GameService\games;

use App\Common\GameCommon;
use App\Utility\BaseEnum;
use App\Common\AuthCommon;
use Swoole\Timer;
use App\Utility\RedisTools;
use App\Enum\MahjEnum;
use App\Common\BaseComm;
use App\Enum\CardEnum;
use App\Enum\SysEnum;
use EasySwoole\EasySwoole\Swoole\Task\TaskManager;
use App\Common\PlayCommon;

/**
 * 抢庄牌九游戏
 *
 * @author Administrator
 */
class PlayGrabNine extends BasePlay
{
    // 事件
    public function __construct()
    {
        //配置时间:时间问题
        $this-> gtimeConfig = array(
            "quangzhuang_long" => 10,   //抢庄时长
            "waitbet_long" => 5,        //等待下注
            "playbet_long" => 15,       //下注时长
            "playsaizi_long" => 2,      //下注完成，等待摇甩子时长
            "waitcard_long" => 2,       //摇甩子完成，等待发牌时长
            "waitdeal_long" => 6,       //摇甩子完成，等待结算时长
            "nextgame_long" => 15,      //进入第二局默认等待时长
            "nextgamestart_long" => 3,       //下一局启动前3秒倒计时
        );
    }
    
    
    

    /**
     * TODO 牌九游戏入口；
          *     必须实现这个方法；
          *     游戏开始逻辑
     */
    public function asycStartGame($gameNo, $gameRId = FALSE, $fjInfo = FALSE, &$commonLoopTimer = 0)
    {
        $gameId = isset($fjInfo['gameId']) ? intval($fjInfo['gameId']) : 0;
        $gameAllData = BaseComm::distMemoryRooms($gameNo, FALSE); // 内存数据；
        $fjSta = isset($gameAllData['fjStatus']) ? intval($gameAllData['fjStatus']) : 0;
        $firstStamp = isset($gameAllData['create']) ? intval($gameAllData['create']) : 0;
        $gamePlays = isset($gameAllData['lists']) ? $gameAllData['lists'] : FALSE; // 玩家列表；
        $playFds = ! empty($gamePlays) ? array_column($gamePlays, "fd", "seatNo") : false; // 座位列表
        BaseEnum::Logger('取内存数据:Games-(PlayGrabNine)-[asycStartGame]--->>>' . json_encode($gameAllData), 'user-login');

        //TODO 开始就给玩家生成牌的数据；
        $useCards = GameCommon::createCardList($gameId, $gameNo, false); // 启动发牌 ： XYM_0X19；
        BaseEnum::Logger("游戏开始生成牌:Games-(PlayGrabNine)-{$gameId} 再计算计算谁是[{$gameNo}]庄主---->>>" . json_encode($useCards), 'user-login'); // 所有玩家FD
        $Parms = [
            "gameNo" => $gameNo,
            "clientIds" => $playFds,
            "status" => $fjSta
        ];
        
        //TODO 开启机器人自动抢庄
        $this->startRobotDoQiangz($gameId, $gameNo);

        // 抢庄====倒计时10秒钟
        $qzLong = $this->getConfigTimeLong('quangzhuang_long') ?? 5;
        
        //TODO 抢庄倒计时结束     
        Timer::after($qzLong * 1000,function () use ($gameNo, $gameRId, $gamePlays, $playFds, $fjInfo, $useCards,$firstStamp) {
            $error = '';
            $notQzUser = FALSE;
            PlayHelp::getInstance()->setCommTimerStop($gameNo,FALSE);           // 清空当前游戏中的KEY对应的定时器
            if (! $fjInfo) {
                $fjInfo = BaseComm::GameRoomDetail($gameRId);
            }
            
            //所有玩家；
            $allPeops = ! empty($gamePlays) ? array_column($gamePlays, "points", "seatNo") : false; // 座位列表
            $gameId = isset($fjInfo['gameId']) ? intval($fjInfo['gameId']) : 0;
            BaseEnum::Logger("选庄家定时器：Games-(PlayGrabNine)-==========>  ，计算谁是[{$gameNo}]坐庄---->>>" . json_encode($playFds), 'user-login'); // 所有玩家FD
            
            //TODO 选庄家
            $zjUser = GameCommon::countRoomMaimZhuang($gameNo, $gamePlays, $notQzUser);
            $zjUid = isset($zjUser['uid']) ? intval($zjUser['uid']) : 0; // 抢到倍数大于0
            $zjSeatid = isset($zjUser['seatNo']) ? intval($zjUser['seatNo']) : - 1; // 抢到倍数大于0
            $zjMuts = isset($zjUser['mutdotts']) ? intval($zjUser['mutdotts']) : 0; // 抢到倍数大于0
            $zjPoints = isset($zjUser['points']) ? floatval($zjUser['points']) : 0; // 抢到倍数大于0
            BaseEnum::Logger("抢庄统计：Games-(PlayGrabNine)-哪些用户没有抢庄 （庄家[{$zjUid}]：抢{$zjMuts}倍，余额数：{$zjPoints}）===> " . json_encode($notQzUser), 'user-login');
            if (! empty($notQzUser)) { // 显示不抢庄的玩家
                foreach ($notQzUser as $onenotxj) {
                    $pushdata = ['m' => BaseEnum::XYM_0X18,  "data" => $onenotxj ];
                    AuthCommon::pushFrontWeb($playFds, json_encode($pushdata)); // TODO 推送不抢庄
                }
            }
            if (isset($zjUser) && $zjUid > 0 && isset($zjUser['fd']) && intval($zjUser['fd']) > 0 && ! empty($playFds) && $zjMuts > 0 && $zjPoints > 0) { // 抢zhuang 成功
                $showList = array(
                    "seatNo" => $zjSeatid,
                    "mutdotts" => $zjMuts,              // 庄家下注倍数
                    "max_mutle" => 1,                   // 其它玩家的最大下注倍数
                    "nums" => sizeof($playFds)          // 通知给所有玩家，谁是庄家；
                ); 

                // TODO 抢庄成功；计算每个用户最大倍数 ；不超过最大值（庄家/闲家输的值 ＝ 房间底注 * 抢庄倍数 * 下注倍数 ）；
                $maxMutl = isset($fjInfo["maxMultiple"]) ? intval($fjInfo["maxMultiple"]) : 0;      // 房间最高倍不超过最大值；
                $everyMoney = isset($fjInfo["everyPoints"]) ? floatval($fjInfo["everyPoints"]) : 0; // 房间底注；
                if(!$everyMoney || $everyMoney<=0) $everyMoney = 1 ;
                
                //TODO 每个位置上金额 计算最大下注倍数===取一个最小值；
                $minMutVals = [];
                foreach ($allPeops as $setid => $pemoney){
                    $countMuts = ($zjMuts > 0) ? $zjMuts :1;                //TODO　(倍数计算不能为0)
                    if (isset($pemoney) && floatval($pemoney) > 0 && $setid !== $zjSeatid) {
                        $tempMutl = intval($pemoney / ($everyMoney * $countMuts));                 //输的分数
                        if ($maxMutl > 0 && $tempMutl > $maxMutl ) {  $tempMutl = $maxMutl; } 
                        $minMutVals[$setid] = $tempMutl;                                           //输的分数
                    }
                }
                $showList["max_mutle"] = !empty($minMutVals) ? min($minMutVals) : 1;                // 最大下注倍数
                
                //TODO  游戏记录写DB数据; ======> 记录玩家中谁是庄家及倍数；
                $editData = array( "ptype" => 1, "userId" => $zjUid,  "data" => $zjMuts );
                $maxut = GameCommon::updateRoomsPInfo($gameNo, $editData, $gamePlays, $error);
                if (isset($maxut) && $maxut === true) {

                    //TODO 获取起始时间间隔
                    $countStamp = BaseEnum::getNowTimeLong($firstStamp);
                    BaseComm::gameLogger($gameNo,"开局第{$countStamp}秒  , 定庄完成, {$zjSeatid} 号位为庄家 ({$zjMuts}倍),最高下注倍数：" . $showList["max_mutle"]);            //TODO 对局开始日志

                    //TODO 推送到所有FD，庄家的座位号；[XYM_0X19 --抢庄成功 ]
                    BaseEnum::Logger("抢庄成功：Games-(PlayGrabNine)-恭喜用户 UID={$zjUid} 抢庄(QZ：{$zjMuts}倍)成功---->>> SUCCESS !!!!" . $gameNo, 'user-login');
                    $pushArray = [ 'm' => BaseEnum::XYM_0X1A, "data" => $showList];
                    AuthCommon::pushFrontWeb($playFds, json_encode($pushArray));    // 推送相同内容
                    
                    
                    //TODO 启动下一个定时器==>闲家选倍数
                    $zjUserInfo = ["uid" => $zjUid,  "seatNo" => $zjSeatid,  "bet_mutle" => $zjMuts,  "main_flag" => 1];
                    $this->StartUserBetMuts($gameId, $gameRId, $gameNo, $gamePlays, $zjUid, $zjUserInfo, $useCards);
                } else {
                    // 异常失败
                    AuthCommon::pushFrontWeb($playFds, json_encode([
                        'm' => $maxut,
                        "data" => null,
                        "msg" => $error
                    ])); 
                }
            } else {
                BaseEnum::Logger('抢庄失败：PlayGrabNine-(asycStartGame)-执行定时器计算抢庄失败 ---->>>Fail !!!!' . $gameNo, 'user-login');
            }
        });
    }

    /**
     * 游戏玩家下注实现(下注把倍数存入内存中处理)
     *
     * @param string $gameNo
     * @param int $roomId
     * @param boolean $params
     */
    public function asycPlayBets($roomId, $gameNo, $params = FALSE, & $errorNo = -1)
    {
        $userId = isset($params['loginUid']) ? intval($params['loginUid']) : 0;
        $qmutil = isset($params['qmutil']) ? intval($params['qmutil']) : 1; // 下注倍数
        if (! $roomId || empty($gameNo) || empty($params) || $userId <= 0) {
            return FALSE;
        }
        $gameDetail = BaseComm::distMemoryRooms($gameNo, FALSE); // 内存数据；
        $fjStatus = isset($gameDetail['fjStatus']) ? intval($gameDetail['fjStatus']) : 0;
        $users = isset($gameDetail['lists']) ? $gameDetail['lists'] : FALSE;

        // 庄家ID
        $zjUid = 0;
        if (! empty($users)) {
            foreach ($users as $oneuu) {
                if (isset($oneuu['uid']) && intval($oneuu['uid']) > 0 && isset($oneuu['mainflag']) && intval($oneuu['mainflag']) == 1) {
                    $zjUid = intval($oneuu['uid']);
                    break;
                }
            }
        }

        BaseEnum::Logger("游戏数据 : (asycPlayBets - [{$gameNo}])==>" . json_encode($gameDetail), 'user-login');
        if (! $gameDetail || ! $users) {
            $errorNo = BaseEnum::MSG_0XFA;
            return FALSE;
        }

        // 非下注状态；
        if (! $fjStatus || $fjStatus !== 1) {
            $errorNo = BaseEnum::MSG_0XFD;
            return FALSE;
        }

        // 庄家不能下注
        if ($zjUid == $userId) {
            $errorNo = BaseEnum::MSG_0XF2;
            return FALSE;
            ;
        }

        $userSeat = array_column($users, "seatNo", "uid");
        $userFds = array_column($users, "fd", "seatNo");

        // TODO 实现下注；写入数据进到内存中（结构:lists为玩家信息，bets 下注列表）
        $userBets = isset($gameDetail['bets']) ? $gameDetail['bets'] : [];
        $pushObj = FALSE;
        foreach ($userSeat as $uuid => $seatIndex) {
            if ($uuid && intval($uuid) === $userId) {
                $userBets[$seatIndex] = $qmutil; // 下注倍数存起放在内存中
                $pushObj = [
                    "seatNo" => $seatIndex,
                    "mutdotts" => $qmutil,
                    "p_fd" => $userId
                ];
                break;
            }
        }

        // 下注数据存起来
        $gameDetail['bets'] = $userBets;
        BaseComm::distMemoryRooms($gameNo, TRUE, $gameDetail);

        // 下注信息
        $pushData = array(
            "m" => BaseEnum::XYM_0X19,
            "data" => $pushObj
        );
        BaseEnum::Logger("玩家下注成功 : (asycPlayBets - [{$gameNo}])==>" . json_encode($pushObj), 'user-login');
        AuthCommon::pushFrontWeb($userFds, json_encode($pushData)); // 通知给所有玩家，谁在抢庄；

        // TODO 下注人员都满员启动提前结束定时器
        if (! empty($userBets) && sizeof($userBets) > 0 && sizeof($userBets) + 1 >= sizeof($userSeat)) {
            PlayHelp::getInstance()->setCommTimerStop($gameNo, true); // 停掉定时器
            BaseEnum::Logger("所有玩家下注完成：========：(asycPlayBets) -- [ STOP ]-RoomId=" . $gameNo . "  =======================设置为1,中断操作========>>>>>>", 'user-login');
        }

        return TRUE;
    }

    
    /**
     * TODO 当前游戏类型：下一步执行功能
     * @param int $type  类型定义type=1为抢庄动画播完，type=2为结算动画播完
     * @param int $loginFd
     * @param string $gameNo
     * @param array $fjDetail
     * @param int $getUix
     */
    public function doNextSteps($type,$loginFd, $gameNo, $fjDetail, $getUix) {
        $fjDetail = BaseComm::distMemoryRooms($gameNo, FALSE);                      // 房间内的信息；
        if (isset($type) && intval($type) == 1) {
            if (isset($fjDetail["doNext"]) && intval($fjDetail["doNext"]) > 0) {    // 已经处理  === 查看当前状态是否启动；
                BaseEnum::Logger('下注执行停止：PlayGrabNine-(doNextSteps)-[检查到doNext=1 已经执行处理过，不再处理]--->>>' . json_encode($fjDetail), 'user-login');
                return FALSE;
            }
            $fjDetail["doNext"] = 1;
            BaseComm::distMemoryRooms($gameNo,TRUE,$fjDetail);      // 保存标识进入下一步
            //TODO 抢庄动画播完；触发下注
            $this->doNextPlayBets($loginFd, $gameNo, $fjDetail, 0);
        }
        else{
            //TODO 结算动画播完；触发第二局开始
            $gameId = isset($fjDetail['gameId']) ? intval($fjDetail['gameId']) :0;
            $gameRId = isset($fjDetail['grid']) ? intval($fjDetail['grid']) :0;
            $this -> WaitReStart($gameId, $gameRId, $gameNo);
        }
    }
    
    /**
     * TODO 进入下一步处理事件
     */
    public function doNextPlayBets($loginFd, $gameNo, $fjDetail = FALSE, $getUix = FALSE) {
        BaseEnum::Logger("TEST：传入-(doNextPlayBets)-= [{$gameNo}]-->>>" . json_encode($fjDetail), 'user-login');
        if(!$loginFd){
            $fjDetail = BaseComm::distMemoryRooms($gameNo, FALSE);
        }
        $gameId = isset($fjDetail['gameId']) ? intval($fjDetail['gameId']) : 0;
        $gameRId = isset($fjDetail['grid']) ? intval($fjDetail['grid']) : 0;
        $fjSta = isset($fjDetail['fjStatus']) ? intval($fjDetail['fjStatus']) : 0;              // 动画处理完触发；只能调用一次；触发动画倒计时；
        $gamePlays = isset($fjDetail['lists']) ? $fjDetail['lists'] : FALSE;                    // 玩家列表；
        $playerFds = ! empty($gamePlays) ? array_column($gamePlays, "fd", "seatNo") : false;    // 座位列表
        $Parms = [
            "gameNo" => $gameNo,
            "clientIds" => $playerFds,
            "status" => $fjSta
        ];
        $zjUserInfo = FALSE;
        if (! empty($gamePlays) && sizeof($gamePlays) >= 2) { // 查找庄家信息；
            foreach ($gamePlays as $oneuser) {
                if (isset($oneuser["uid"]) && intval($oneuser["uid"]) > 0 && isset($oneuser["mainflag"]) && intval($oneuser["mainflag"]) === 1) { // 庄家 mainflag = 1
                    $zjUserInfo = array(
                        "uid" => $oneuser["uid"],
                        "seatNo" => @$oneuser["seatNo"],
                        "bet_mutle" => isset($oneuser['mutdotts']) ? intval($oneuser['mutdotts']) : 0,
                        "main_flag" => 1 // 标识为庄主;
                    ); break;
                }
            }
        }

        // 标识为庄主;
        if (! $zjUserInfo || ! isset($zjUserInfo['uid']) || intval($zjUserInfo['uid']) <= 0){
            return FALSE;
        }
            
        //TODO 开启机器人自动下注功能
        $this->startRobotDoBets($gameId,$gameRId, $gameNo);

        //TODO 启动下注
        $betLong = $this->getConfigTimeLong('playbet_long') ?? 15;
        $this->asycTimeStart($betLong, $gameNo, $Parms, function () use ($gameId,$gameRId,$gameNo, $gamePlays, $playerFds, $zjUserInfo) {
            $zjUserid = isset($zjUserInfo["uid"]) ? intval($zjUserInfo["uid"]) : 0;

            // 房间内的数据
            $gameDetail = BaseComm::distMemoryRooms($gameNo, FALSE);
            $gameId = isset($gameDetail['gameId']) ? intval($gameDetail['gameId']) : 0;
            $gameRId = isset($gameDetail['grid']) ? intval($gameDetail['grid']) : 0;
            $userBets = isset($gameDetail['bets']) ? $gameDetail['bets'] : [];          // 下注的数据内容
            $gamePlays = isset($gameDetail['lists']) ? $gameDetail['lists'] : FALSE;    // 玩家列表；
            $firstStamp = isset($gameDetail["create"]) ? intval($gameDetail["create"]) : 0;

            $allSeat = array_column($gamePlays, 'seatNo', "uid");
            //TODO 获取起始时间间隔
            $countStamp = BaseEnum::getNowTimeLong($firstStamp);
            $betLoggs=["开局第{$countStamp}秒  , 下注"];
            /**
             * TODO 下注==== 玩家下注 步骤：
             * 1、检查所有玩家是否有下注和下注倍数据
             * 2、把没有下注完的默认下1倍；并推给所有玩家
             */
            BaseEnum::Logger("执行定时器计算：PlayGrabNine-(asycStartUserBetMuts)-=====[投注倍数]  ，去列队中给[{$gameNo}]所有玩家下单--（DB）-->>>已下注：" . json_encode($userBets), 'user-login');
            BaseEnum::Logger('取内存数据：PlayGrabNine-(BaseDB::gameRoomPlayerData)-[asycStartUserBetMuts]--->>>' . json_encode($gameDetail), 'user-login');

            $userBetIds = [];
            $noticeUsers = [];  // TODO 计算未下注的用户信息
            foreach ($allSeat as $uuid => $setid2) {
                if (isset($uuid) && intval($uuid) > 0 && intval($uuid) === $zjUserid) continue;
                $usrbetMul = isset($userBets[$setid2]) ? intval($userBets[$setid2]) : 0; // 下注倍数
                if ($uuid > 0 && $usrbetMul <= 0) {
                    $userBets[$setid2] = 1;
                    $noticeUsers[$uuid] = array( "uid" => $uuid, "seatNo" => $setid2, "bet_mutle" => 1);
                }
                $betLoggs[] = "{$setid2} 号位  下注{$usrbetMul} 倍";
                $userBetIds[] = $uuid; // 下注列表
            }

            // TODO 修改内存中游戏状态
            foreach ($gamePlays as $setiid => &$oneplay) {
                if (isset($userBets[$setiid]) && intval($userBets[$setiid]) > 0 && !empty($oneplay)) {
                    $oneplay["mutdotts"] = intval($userBets[$setiid]);  // 修改下注倍数
                    $oneplay["pstatus"] = CardEnum::GAME_STEP_2;        // 修改下注成功；
                }
            }
            $gameDetail['bets'] = $userBets;
            $gameDetail['lists'] = $gamePlays;
            $gameDetail['fjStatus'] = CardEnum::GAME_STEP_2;            // 下注成功
            BaseComm::distMemoryRooms($gameNo, TRUE, $gameDetail);      // TODO 存储游戏数据
            BaseEnum::wirteRedisData(SysEnum::ONLINE_ROOMS_LIST_KEY . $gameNo, json_encode($gameDetail));

            // TODO 所有玩家订单入库处理： (闲家下注)
            $error = '';
            $savedata = array(
                "ptype" => 2,
                "userId" => implode(',', $userBetIds),
                "data" => $userBets
            );
            $dbFlag = GameCommon::updateRoomsPInfo($gameNo, $savedata, $gamePlays, $error); // 下注成功；

            if ($dbFlag !== FALSE) {
                BaseEnum::Logger("闲家下注失败：(下注) ->>> ERROR，  未下注玩家：" . json_encode($noticeUsers), 'user-login');
            }
            BaseComm::gameLogger($gameNo,$betLoggs);       //下注日志
            // TODO 单个数据推到前端；
            $allFds = array_column($gamePlays, "fd", "seatNo");
            if (! empty($noticeUsers)) {
                $noticeUsers = array_column($noticeUsers, null, "seatNo"); // 通知给所有玩家，下注倍数；
                foreach ($noticeUsers as $siteid => $oneline) {
                    $oneInfo = [
                        "seatNo" => $siteid,
                        "mutdotts" => isset($oneline['bet_mutle']) ? intval($oneline['bet_mutle']) : 1,
                        "self" => 1
                    ];
                    $pushdata = [
                        'm' => BaseEnum::XYM_0X19,
                        "data" => $oneInfo
                    ];
                    AuthCommon::pushFrontWeb($allFds, json_encode($pushdata)); // 推送未下注玩家默认1倍
                }
            }

            $Long = $this->getConfigTimeLong('playsaizi_long') ?? 2;
            // TODO 启动摇色子[XYM_0X30]
            Timer::after($Long* 1000, function () use ($gameId, $gameRId, $gameNo, $allFds) {
                BaseEnum::Logger("启动摇色子：(摇色子) ->>> Start，  未下注玩家：" . json_encode([ $gameId, $gameRId, $gameNo ]), 'user-login');
                $this -> StartCards($gameId, $gameRId, $gameNo, $allFds);
            });

            return FALSE;
        });
    }

    
    
    /**
     * TODO 掉线重连操作
     *
     * @param int $userid
     * @param int $loginFd
     * @param string $newGameNo
     */
    public function reConnectPlay($userid, $loginFd, $gameNo = FALSE)
    {
        if (! $gameNo || $userid <= 0)
            return FALSE; // 玩家最新游戏ID对应的号
            $fjData = BaseComm::distMemoryRooms($gameNo, FALSE);
            $gameId = isset($fjData['gameId']) ? intval($fjData['gameId']) : 0;
            $gameStatus = isset($fjData['fjStatus']) ? intval($fjData['fjStatus']) : 0;
            $gamePlays = isset($fjData['lists']) ? $fjData['lists'] : FALSE;
            // 只要游戏没有结束就换掉用户的客户端FD值，否则推送出错；
            if (! empty($gamePlays)) {
                foreach ($gamePlays as $r => $oneline) {
                    if (isset($oneline['uid']) && intval($oneline['uid']) === $userid) {
                        $gamePlays[$r]['fd'] = $loginFd; // 当前登录用户的客户FD值；
                    }
                }
                $fjData['lists'] = $gamePlays; // 修改后的数据重新给值
                BaseComm::distMemoryRooms($gameNo, TRUE, $fjData); // 保存数据
                // 游戏数据恢复
                BaseEnum::Logger("掉线重连：PlayNiuNiu --> reConnectPlay-[{$userid},$gameId ,$gameNo]-------gameData-->" . json_encode($fjData), 'user-login');
                // 查询当前游戏状态：
                if (! empty($fjData) && $gameStatus >= 0 && in_array($gameStatus, [
                    0,
                    1,
                    2,
                    3,
                    4,
                    5
                ])) { // 大于1 都是游戏中；
                    $steps = BaseEnum::GAME_STEP;
                    $stepName = isset($steps[$gameStatus]) ? $steps[$gameStatus] : "-";
                    BaseEnum::Logger("掉线重连：PlayNiuNiu --> 当前状态：-[{$stepName},$gameNo]-------status-->" . $gameStatus, 'user-login');
                    // 显示游戏中的数据结构：定义新的结构；
                    $returnData = array(
                        "gameNo" => $gameNo,
                        "step" => $stepName,
                        "status" => $gameStatus,
                        "lists" => []
                    );
                    // 恢复游戏数据： 只处理以下几张情况；
                    // 1、抢庄中---谁庄过庄（标识出来），
                    // 2、下注中(谁是庄家标识出来，)
                    // 3、结算中(庄家，下注倍数，当前玩家牌值内容)
                    $gameResult = $this->reBackData($userid, $gameStatus, $gameNo, $gamePlays);
                    BaseEnum::Logger("掉线重连：PlayNiuNiu --> 数据恢复：-[{$gameNo}]-------status-->" . json_encode($gameResult), 'user-login');
                    if (! $gameResult) {
                        return FALSE;
                    }
                    // 显示每个玩家的数据内容；
                    $returnData["lists"] = $gameResult;
                    return $returnData;
                }
            }
            return FALSE;
    }
    
    
    /**
     * TODO 恢复游戏数据；
          * 处理以下几张情况；
     * 1、抢庄中---谁庄过庄（标识出来），
     * 2、下注中(谁是庄家标识出来，)
     * 3、结算中(庄家，下注倍数，当前玩家牌值内容)
     *
          * 附状态值：0 => '匹配', 1 => '抢庄', 2 => '下注', 3 => '发牌', 4 => '拼牌', 5 => '结算',
     *
     * @param int $gameStatus
     * @param array $gamePlays
     */
    public static function reBackData($loginUid, $gameStatus, $gameNo = FALSE, $gamePlays = FALSE)
    {
        $dealStatus = [ CardEnum::GAME_STEP_2, CardEnum::GAME_STEP_3 ];
        $finishStatus = [ CardEnum::GAME_STEP_4,  CardEnum::GAME_STEP_5 ];
        if (! $loginUid || ! $gamePlays || ! $gameNo)
            return false;
        $redisobj = new RedisTools();
        $redisobj->select(0);
        $palyUser = $gamePlays;
        foreach ($palyUser as & $oneus) {
            unset($oneus['fd']);
            unset($oneus['agentId']);
            unset($oneus['betmoney']);
        }
        //TODO 匹配成功 开始抢庄 ===> 谁有抢庄
        if ($gameStatus === CardEnum::GAME_STEP_0) {
            foreach ($palyUser as $sid => $oneuser) {
                $palyUser[$sid]['pstatus'] = '玩家抢庄';
            }
        } //TODO 抢庄完成 开始下注 ===> 标出谁是庄家,闲家下注倍数；
        else if ($gameStatus === CardEnum::GAME_STEP_1) {
            $gameDetail = BaseComm::distMemoryRooms($gameNo,FALSE);
            $betArray = isset($gameDetail['bets']) ? $gameDetail['bets'] : [];  // 查询下注数据；
            foreach ($palyUser as $sid => $oneuser) {
                $palyUser[$sid]['pstatus'] = '闲家下注';
                if (isset($oneuser['mainflag']) && intval($oneuser['mainflag']) == 1) { // 定庄：uid,
                    $palyUser[$sid]['text'] = '庄';
                    $palyUser[$sid]['pstatus'] = '等待闲家下注';
                } else {
                    $palyUser[$sid]['text'] = '闲';
                    $palyUser[$sid]['mutdotts'] = isset($betArray[$sid]) ? intval($betArray[$sid]) : 0; // 闲家下注倍数
                    $palyUser[$sid]['max_mutle'] = 3;   // 最大下注倍数；
                }
            }
        } //TODO 下注完成 开始发牌 拼牌 ===> 开始发牌（显示下注倍数）
        else if (in_array($gameStatus, $dealStatus , true)) {
            $cards = BaseComm::distMemoryCards($gameNo, FALSE); // 全局变量获取手牌数据（座位号seatNo=> [11,23,233]）
            foreach ($palyUser as $sid => $oneuser) {
                $palyUser[$sid]['pstatus'] = ($gameStatus == 3) ? "拼牌中" : '发牌中';
                $palyUser[$sid]['ifcards'] = 1; // 已发牌
                $palyUser[$sid]['cards'] = false; // 已发牌
                if (isset($oneuser['uid']) && intval($oneuser['uid']) == $loginUid) { // 显示发给我的手牌；
                    $palyUser[$sid]['cards'] = isset($cards[$sid]) ? $cards[$sid] : [];
                }
            }
        } //TODO 拼牌完成 摊牌 ===> 结算数据（显示玩家最后的结算数据）
        else if (in_array($gameStatus, $finishStatus, true)) {
            $cards = BaseComm::distMemoryCards($gameNo, FALSE); // 全局变量获取手牌数据（座位号seatNo=> [11,23,233]）
            foreach ($palyUser as $sid => $oneuser) {
                $palyUser[$sid]['pstatus'] = "结算中";
                $palyUser[$sid]['ifcards'] = 1; // 已发牌
                $palyUser[$sid]['cards'] = isset($cards[$sid]) ? $cards[$sid] : [];
                //TODO 显示结算所的金额数据；
                $palyUser[$sid]['lastmoney'] = 0.01;
            }
        }
        return $palyUser;
    }

    /**
     * TODO 结算牌九的逻辑处理
     * @param boolean $zjdata
     * @param boolean $xjdata
     * @param boolean $fjdata
     * @param boolean $returndata
     */
    public static function dealRoomGameQZGRAB($zjdata = false, $xjdata = false, $fjdata = false, &$returndata = false)
    {
        $zjNiuLevel = 0; // 比牌操作；庄家跟每个闲家一一对比；
        $zjTotalMoney = 0; // 庄家的总钱
        $gameUtype = isset($fjdata["gpKey"]) ? $fjdata["gpKey"] : "";                       // 游戏类型
        $fjmoney = isset($fjdata["everyPoints"]) ? floatval($fjdata["everyPoints"]) : 0;    // 房间低注金额
        $fjmaxPeop = isset($fjdata["maxPlayer"]) ? intval($fjdata["maxPlayer"]) : 0;        // 房间低注金额
        $zjcode = isset($zjdata['cards']) ? $zjdata['cards'] : false;                       // 庄家的牌数据；
        $zjMutle = isset($zjdata['mainMutle']) ? intval($zjdata['mainMutle']) : 1;          // 庄家倍数；
        $zjSeatNo = isset($zjdata['seatNo']) ? intval($zjdata['seatNo']) : 0;               // 庄家座位号；
        $zjUserId = isset($zjdata['userId']) ? intval($zjdata['userId']) : 0;               // 庄家UID；
        $zjFees = [];
        $allCards = [ $zjSeatNo => $zjcode];
        $moneyArr = [$zjSeatNo => []];
        $zjRes = MahjEnum::dealGrabNineLevel($zjcode, $zjNiuLevel);
        if (! empty($zjdata) && ! empty($xjdata) && sizeof($xjdata) > 0 && ! empty($zjRes) && $fjmoney > 0 && $zjSeatNo >= 0) {
            $zjCardmutl = isset($zjRes['mutl_value']) ? $zjRes['mutl_value'] : 1; // 牌型倍数
            $zjRes['cards'] = $zjcode;
            //echo '庄-><br>'.print_r($zjRes,true);
            foreach ($xjdata as $xjinfo) {
                $xjLevel = - 1; // 闲家牌型等级
                $xjMoney = 0;   // 闲家赢的钱
                $xjWater = 0;
                $xjBets = isset($xjinfo['mainMutle']) ? intval($xjinfo['mainMutle']) : 1; // 闲家下注倍数；
                $xianjCard = isset($xjinfo['cards']) ? $xjinfo['cards'] : false;
                $xjSeatNo = isset($xjinfo['seatNo']) ? intval($xjinfo['seatNo']) : 0; // 庄家座位号；
                $xjUserId = isset($xjinfo['userId']) ? intval($xjinfo['userId']) : 0; // 庄家座位号；
                $allCards[$xjSeatNo] = $xianjCard;
                $xjRes = MahjEnum::dealGrabNineLevel($xianjCard, $xjLevel);
                $lastWin = 1; // 通过计算谁家赢：1-庄赢，2-闲赢,
                if (! empty($xianjCard) && ! empty($xjRes) && $xjLevel >= 0 && $xjSeatNo >= 0 && isset($xjUserId) && intval($xjUserId) >0) {
                    $xjCardMut = isset($xjRes['mutl_value']) ? $xjRes['mutl_value'] : 1; // 牌型倍数
                    $xjRes['cards'] = $xianjCard;
                    //echo "闲->{$xjSeatNo}<br>".print_r($xjRes,true);
                    // 比大小：第一步，比KEY值；
                    if ($zjNiuLevel > $xjLevel) { // 牌型相比； 庄赢；
                        $lastWin = 1;
                    } else if ($zjNiuLevel < $xjLevel) { // 牌型相比； 闲赢；
                        $lastWin = 2;
                    } else if ($zjNiuLevel == $xjLevel) { // 牌型相同，比单张的大小点数；
                        $lastWin = self::gatherWhoWin($zjRes, $xjRes, $xjLevel);
                    }
                    
                    //TODO 计算输赢的钱数据；
                    $zjMoney = self::countWinMoney($lastWin, $fjmoney, $zjMutle, $zjCardmutl, $xjBets, $xjCardMut, $xjMoney);
                    
                    //和局情况处理 $lastWin = 100
                    $xjLastMoney = $xjMoney;
                    if($xjMoney > 0){
                        $xjWater = bcmul($xjMoney, BaseComm::getSetWaterDott(), 2); // 最后计算抽水的钱
                        $xjLastMoney = bcsub($xjMoney, $xjWater,2);
                    }
                    $zjTotalMoney = bcadd($zjTotalMoney, $zjMoney, 2);
                    $zjFees[] = abs($zjMoney);
                    
                    //var_dump("庄家赢的钱： ".$zjMoney . " 总共： ". $zjTotalMoney);
                    $xjBetMoney = abs($xjMoney);    // 玩家的下注的钱:玩家赢的钱，和输的钱：只有正数；
                    $moneyArr[$xjSeatNo] = array(
                        "uid" => $xjUserId,
                        "seatNo" => $xjSeatNo,
                        "mainFlag" => 0,
                        "betMutal" => $xjBets,      //闲家下注倍数据
                        "money" => floatval($xjMoney),
                        "cellScore" =>  $xjBetMoney,                       // 有效下注（取所有位置的下注总和）下多少分
                        "allBetScore" => $xjBetMoney,                      // 所有位下注总分
                        "lastPoints"=>  floatval($xjLastMoney),            // 实际输赢（除去水钱）
                        "level" => $xjLevel,
                        "water" => $xjWater,
                        "card_mutle" => $xjCardMut,
                        "zj" => '闲',
                        "cards" => $xianjCard
                    );
                }
            }

            //TODO  闲家信息没有问题；
            if (! empty($moneyArr) && sizeof($moneyArr) >= 1) {
                $zjWater = 0;
                $allBetZj = !empty($zjFees) ? array_sum($zjFees) : 0 ;
                $zjLastMoney = $zjTotalMoney;
                if (floatval($zjTotalMoney) > 0) {
                    $zjWater = bcmul($zjTotalMoney, BaseComm::getSetWaterDott(), 2); // 最后计算抽水的钱
                    $zjLastMoney = bcsub($zjTotalMoney, $zjWater , 2);
                }
                $moneyArr[$zjSeatNo] = array(
                    "uid" => $zjUserId,
                    "seatNo" => $zjSeatNo,
                    "mainFlag" => 1,
                    "betMutal" => $zjMutle,
                    "money" => floatval($zjTotalMoney),
                    "cellScore" =>  $allBetZj,                          //TODO 有效下注（取所有位置的下注总和）下多少分
                    "allBetScore" => $allBetZj,                         //TODO 所有位下注总分
                    "lastPoints"=>  floatval($zjLastMoney),             //TODO 实际输赢（除去水钱）
                    "level" => $zjNiuLevel,
                    "water" => $zjWater,
                    "card_mutle" => $zjCardmutl,
                    "bet" => '庄',
                    "cards" => $zjcode
                );
                
                //TODO 合计起来，再处理特殊情况 ：庄家钱不够赔的情况
                if(GameCommon::checkUserDealPrice($gameUtype,$moneyArr,$zjUserId) === FALSE){
                    
                    //TODO 防止以小博大，（通过）占比计算 （赢的钱不能超过携带进入的金额）
                    BaseEnum::Logger("检查{$gameUtype}结算数据===>失败 ：" . json_encode($moneyArr),"user-login");
                    return FALSE;
                }
                
                // 手牌信息组合；包含信息：总共多少位置，每个玩家的手牌，庄家位置信息；
                $cardValue = BaseComm::conectUserCardsValue($gameUtype, $fjmaxPeop, $allCards, $zjSeatNo);
                $returndata["cardvalues"] = ! empty($cardValue) ? $cardValue : ""; // 手牌组合数据
            }
        }
        return $moneyArr;
    }

    /**
     * TODO 比较大小；闲赢还是庄赢；
     * @param array $zjRes 庄
     * @param boolean $xjInfo 闲
     * @return boolean
     */
    private static function gatherWhoWin($zjRes, $xjInfo = false, $zjNiuLevel = false)
    {
        $lastWin = 1;
        if ($zjNiuLevel < 10) { // 点数相同比牌值
            //取每个玩家手里的大牌； 进行对比
            $lastWin = MahjEnum::gatherCardSize($zjRes['cards'] , $xjInfo['cards']);
        }
        return $lastWin;
    }

    /**
     * TODO  计算庄输赢数据（不考虑赢家抽水的情况）
     */
    private static function countWinMoney($mainwin = 1, $fjmoney = 0, $zjMutle = 0, $zjCardmutl = 1, $xjBets = 1, $xjCardMut = 1, & $xianMoney = 0)
    {
        // 庄赢 ：庄家胜利所赢 ＝ A * X * Y (庄家下注倍数 ，玩家下注)
        $mainMoney = 0;
        if (intval($mainwin) === 1) {
            $tempMon = $fjmoney * $xjBets * $zjMutle ; //A * X * Y
            $mainMoney = bcsub($tempMon, 0, 2); // 庄家赢的
            $xianMoney = bcsub(0, $tempMon, 2); // 闲家输的
        }else if($mainwin == 100){              //和局的值
            $mainMoney = 0;
            $xianMoney = 0;
        } else {
            // 庄输 A * X * Y
            $tempMon = $fjmoney * $xjBets * $zjMutle; //
            $mainMoney = bcsub(0, $tempMon, 2); // 庄家输的
            $xianMoney = bcsub($tempMon, 0, 2); // 闲家赢的
        }
        return $mainMoney;
    }

    /**
     * TODO 异步监控房间内下注
     * @param int $gameId
     * @param int $gameRId
     */
    protected function StartUserBetMuts($gameId, $gameRId, $gameNo, $players = false, $zjUserid = 0, $zjUserInfo = false, $useCards = FALSE)
    {
        BaseEnum::Logger('闲家异步统计：PlayGrabNine-(StartUserBetMuts)-统计 ---->>>>>>>>>>>>> 待完善:' . $gameNo, 'user-login');
        // 10秒动画效果；
        $waitLong = $this->getConfigTimeLong('waitbet_long') ?? 8;
        Timer::after($waitLong * 1000, function () use ($gameNo, $gameId) {
            $fjDetail = BaseComm::distMemoryRooms($gameNo, FALSE);                  // 房间内的信息；
            if (isset($fjDetail["doNext"]) && intval($fjDetail["doNext"]) > 0) {    // 已经处理  === 查看当前状态是否启动；
                BaseEnum::Logger('异步自动下注执行：PlayGrabNine-(StartUserBetMuts)-[检查到doNext=1 已经执行处理过，不再处理]--->>>' . json_encode($fjDetail), 'user-login');
            } else {
                
                $fjDetail["doNext"] = 1;
                BaseComm::distMemoryRooms($gameNo, TRUE, $fjDetail); // 保存标识不允许再次发起
                $this->doNextPlayBets(null, $gameNo, $fjDetail, 0);
            }
        });
    }

    // TODO 生成色子推送出来
    protected function startRandDott($room_No, $playerFds = FALSE)
    {
        $getAlldata = BaseComm::distMemoryRooms($room_No, FALSE);
        $gameJu = isset($getAlldata['currInning']) ? $getAlldata['currInning'] : 1; // 当前游局号
        $num1 = rand(1, 6); // 摇甩子号码随机处理
        $num2 = rand(1, 6);
        $searray = array(
            $num1,
            $num2
        ); // 摇色子号码
        $seSum = array_sum($searray);
        $firstStamp = isset($getAlldata["create"]) ? intval($getAlldata["create"]) : 0;
        $long = BaseEnum::getNowTimeLong($firstStamp);
        BaseComm::gameLogger($room_No,"开局第{$long}秒 , 摇色子点数");
        // TODO 摇色子
        $sendDott = array(
            'm' => BaseEnum::XYM_0X30,
            "data" => [
                "current" => $gameJu,
                "nums" => $searray,
                "dottod" => $seSum
            ]
        );
        AuthCommon::pushFrontWeb($playerFds, json_encode($sendDott));
    }

    /**
     * TODO 开始发牌操作；
     *
     * @param array $paramList
     */
    protected function StartCards($gameId, $gameRId, $room_No, $playerFds = false)
    {
        // TODO 摇色子
        $this->startRandDott($room_No, $playerFds);
        
        // TODO 暂停3秒发牌
        $sleepTime = $this->getConfigTimeLong('waitcard_long') ?? 3;        
        Timer::after($sleepTime * 1000, function () use ($gameId, $gameRId, $room_No, $playerFds) {
            $playerCards = [];
            $getAlldata = BaseComm::distMemoryRooms($room_No, FALSE);
            $gameId = $getAlldata['gameId'] ?? 0;
            $gameRId =$getAlldata['grid'] ?? 0;
            $firstStamp = isset($getAlldata["create"]) ? intval($getAlldata["create"]) : 0;
            BaseEnum::Logger('取内存数据：PlayGrabNine-(BaseDB::gameRoomPlayerData)-[asycStartCards]--->>>' . json_encode($getAlldata), 'user-login');
            
            $useCards = [] ;//内存取牌数据：
            $memeryCards = BaseComm::distMemoryCards($room_No,FALSE);
            if(!empty($memeryCards)){
                foreach ($memeryCards as $setid => $cardval ){
                    $useCards[$setid] = array("seatNo" => $setid ,"cards" => $cardval , "dottVal" => -1);
                }
            }
            if(empty($useCards)){
                BaseEnum::Logger("重新启动发牌：PlayGrabNine-(StartCards)-；===={$gameId}==>{$room_No}| -> ==allUserFds : ".json_encode($playerFds), 'user-login');
                $useCards = GameCommon::createCardList($gameId, $room_No, false); // 启动发牌 ： XYM_0X19；
            }
            
            // 当前游局号
            $gameJu = isset($getAlldata['currInning']) ? $getAlldata['currInning'] : 1;

            // 发每个用户的手牌数据
            if (isset($useCards) && ! empty($useCards)) {
                $getAlldata['fjStatus'] = CardEnum::GAME_STEP_3; // 修改为发牌成功；
                if (isset($getAlldata['lists']) && ! empty($getAlldata['lists'])) {
                    foreach ($getAlldata['lists'] as $setiid => $fg) {
                        $getAlldata['lists'][$setiid]["pstatus"] = CardEnum::GAME_STEP_3;
                    }
                }
                $long = BaseEnum::getNowTimeLong($firstStamp);
                BaseComm::gameLogger($room_No,"开局第{$long}秒 , 发玩家手牌");
                // 分发给每个用户(分配发牌 )
                foreach ($useCards as $pushOneMsg) {
                    $seatId = isset($pushOneMsg['seatNo']) ? intval($pushOneMsg['seatNo']) : 0;
                    if ($seatId !== false && $seatId >= 0 && isset($pushOneMsg['cards']) && ! empty($pushOneMsg['cards'])) {
                        $playerCards[$seatId] = [
                            "seatNo" => $seatId,
                            "cards" => $pushOneMsg['cards'],
                            "current" => $gameJu,
                            "step" => 1
                        ];
                    }
                }
                // 保存用户数据
                BaseComm::distMemoryRooms($room_No, TRUE, $getAlldata);
                BaseEnum::wirteRedisData(SysEnum::ONLINE_ROOMS_LIST_KEY . $room_No, json_encode($getAlldata)); // 缓存；

                // 启动牌值计算数据；====> 看牌过程
                $this->asycWaitDealCenter($gameRId, $room_No, $playerCards, $playerFds);
                
            } else {
                BaseEnum::Logger('发牌失败：PlayGrabNine-(asycStartCards)-发牌；======>   失败 Fail !! -->  ' . $room_No, 'user-login');
            }
        });
    }

    /**
     * TODO 看牌结算处理；
     *
     * @param string $room_No
     * @param array $useCards
     * @param boolean $playerFds
     */
    protected function asycWaitDealCenter($gameRId, $gameNo, $useCards, $playerFds = FALSE)
    {
        $gameAllData = BaseComm::distMemoryRooms($gameNo, FALSE); // 内存数据；
        $gamePlays = isset($gameAllData['lists']) ? $gameAllData['lists'] : FALSE; // 玩家列表；
        $playerFds = ! empty($gamePlays) ? array_column($gamePlays, "fd", "seatNo") : false; // 座位列表
                                                                                             
        // 结算处理;
        $sleepTime = $this->getConfigTimeLong('waitdeal_long') ?? 2; 
        Timer::after($sleepTime * 1000, function () use ($gameRId, $useCards, $gameNo, $playerFds) {
            $allUserIds=[];
            $allCards = $playerNames = [];

            // 房间信息
            $roomDetail = BaseComm::GameRoomDetail($gameRId);
            // 棋牌内容：
            $fjDetail = BaseComm::distMemoryRooms($gameNo, FALSE);
            $gameId = $fjDetail["gameId"] ?? 0;
            $currentGameInng = isset($fjDetail['currInning']) ? intval($fjDetail['currInning']) : 1; //当前局号
            $userbetList = $fjDetail['lists'] ?? FALSE;
            $firstStamp = isset($fjDetail["create"]) ? intval($fjDetail["create"]) : 0;
            if (! empty($fjDetail) && !empty($userbetList)) {
                $allUserIds = ! empty($userbetList) ? array_column($userbetList, "uid") : false;            // UID
                $playerFds = ! empty($userbetList) ? array_column($userbetList, "fd", "seatNo") : false; // FD防变动
                $playerNames = ! empty($userbetList) ? array_column($userbetList, "name", "uid") : false; // FD防变动
            }
            BaseEnum::Logger("牌九结算处理：PlayGrabNine-(asycWaitDealCenter)-[roomid={$gameRId}]；======> Start ....-->UserList=" . json_encode($userbetList), 'user-login');
            BaseEnum::Logger("牌九结算：PlayGrabNine-(asycWaitDealCenter)-[roomid={$gameRId}]；======> getRoomInfo=" . json_encode($roomDetail), 'user-login');

            // 座位号
            $uSeatList = ! empty($userbetList) ? array_column($userbetList, null, 'seatNo') : false;
            $zjObject = FALSE;          // 比牌数据操作
            $lastNextGameStart = TRUE;  //是否可以进入下一局
            if (! empty($useCards) && ! empty($uSeatList) && ! empty($userbetList)) {
                foreach ($useCards as $seatId => $cardinfo) {
                    $allCards[$seatId] = isset($cardinfo["cards"]) ? $cardinfo["cards"] : false; // 取牌数据
                    $useFo = isset($uSeatList[$seatId]) ? $uSeatList[$seatId] : false;
                    $useCards[$seatId]["userId"] = isset($useFo['uid']) ? intval($useFo['uid']) : 0;
                    $useCards[$seatId]["mainFlag"] = isset($useFo['mainflag']) ? intval($useFo['mainflag']) : 0; // 庄家标识
                    $useCards[$seatId]["mainMutle"] = isset($useFo['mutdotts']) ? intval($useFo['mutdotts']) : 1; // 下注或抢庄的倍数关系
                    if ($useCards[$seatId]["mainFlag"] === 1) {
                        $zjObject = $useCards[$seatId];
                        unset($useCards[$seatId]); // 庄家牌内容
                    }
                }

                // TODO 结算处理；
                BaseEnum::Logger('牌九庄家：PlayGrabNine-(asycWaitDealCenter)===-->' . json_encode($zjObject), 'user-login');
                BaseEnum::Logger('牌九闲家：PlayGrabNine-(asycWaitDealCenter)===-->' . json_encode($useCards), 'user-login');

                $realUserNotices = []; //真实玩家的结算数据
                
                //TODO 结算玩家的计算；把庄家跟闲家进行一一对比；
                $resultList = GameCommon::dealPlayMoney($gameRId, $gameNo, $zjObject, $useCards, $roomDetail,$realUserNotices);  // 处理结果；谁赢钱
                
                // 推送：所有牌数据和输赢；
                if (! empty($resultList) && sizeof($resultList) > 1) {
                    BaseEnum::Logger('牌九结算处理：PlayGrabNine-(asycWaitDealCenter)-；======> SUCCESS ....-->  ' . json_encode($resultList), 'user-login');
                    $fjDetail['fjStatus'] = CardEnum::GAME_STEP_5;
                    if (isset($fjDetail['lists']) && ! empty($fjDetail['lists'])) {
                        foreach ($fjDetail['lists'] as $setiid => $fg) {
                            $fjDetail['lists'][$setiid]["pstatus"] = CardEnum::GAME_STEP_5;
                        }
                    }
                    //TODO 获取起始时间间隔
                    $countStamp = BaseEnum::getNowTimeLong($firstStamp);
                    $gameLoggs= ["开局第{$countStamp}秒 , 结算"];
                    BaseComm::distMemoryRooms($gameNo, TRUE, $fjDetail); // 保存用户数据
                    BaseEnum::wirteRedisData(SysEnum::ONLINE_ROOMS_LIST_KEY . $gameNo, json_encode($fjDetail));     // 缓存；
                    $minDiMoney = isset($roomDetail["everyPoints"]) ? intval($roomDetail["everyPoints"]) : 1;       // 底注钱
                    $minRoomFee = isset($roomDetail["smallPoints"]) ? intval($roomDetail["smallPoints"]) : 1;       // 最少进入房间底注
                    $redisArray = []; //TODO 结算汇总数据
                    foreach ($resultList as $setid => $res) {
                        $set_uid = isset($res['uid']) ? intval($res['uid']) : 0;
                        $set_mutle = isset($res['betMutal']) ? intval($res['betMutal']) : 0;
                        $set_points = isset($res['points']) ? floatval($res['points']) : 0;
                        $changepoints = isset($res['changeMoney']) ? floatval($res['changeMoney']) : 0;
                        $cellfee = $res['cellScore'] ??0; //有效投注
                        $first_points = bcsub($set_points, $changepoints,2) ;

                        //TODO 判断points是否可以继续下一局游戏
                        if ($minRoomFee >0 && $set_points < $minRoomFee) {
                            $lastNextGameStart = FALSE;
                        }
                        
                        //历史订单记录存入REDIS中；
                        $userFor = ['uid'=>$set_uid,"name"=> $playerNames[$set_uid] ?? "***","mainFlag"=> FALSE,"level"=> @$res['level'],"firstPoints" =>$first_points,"changeMoney" => $changepoints,"roomEvery"=> $minDiMoney ,"qzMutal" =>FALSE,"betMutal" => $set_mutle ];
                        if (isset($res['mainFlag']) && intval($res['mainFlag']) === 1) {
                            $userFor['qzMutal'] = $set_mutle;
                            $userFor['betMutal'] = FALSE; //庄家
                            $userFor['mainFlag'] = TRUE ; //庄家
                        }
                        
                        $redisArray[$set_uid] = $userFor;   //玩家信息存储起来
                        $cards2 = $res["cards"] ?? [];
                        if (empty($cards2)){
                            $cards2 = $allCards[$setid] ?? [];
                            $resultList[$setid]["cards"] = $cards2; // 取牌数据
                        }

                        $gameLoggs[] = "{$setid} 号位  本局输赢:{$changepoints},下注分：{$cellfee},牌值：".json_encode($cards2);
                    }
                    
                    // TODO 缓存结算汇总数据
                    $gameNoLastKey = SysEnum::ONLINE_ROOMS_TONGJI_KEY.substr($gameNo, -10);
                    RedisTools::Pool_hSet($gameNoLastKey,"game_{$currentGameInng}", json_encode($redisArray));
                    
                    //TODO 不足的情况下，加上虚拟人员
                    $lastDeals = array_values($resultList);                 // 最后的结算数据；
                    if (! empty($lastDeals) && sizeof($lastDeals) < 4) {    // 不足的情况下，加上虚拟人员
                        $Card2 = PlayHelp::getInstance()->getRoomCards($gameNo);
                        for ($set2 = sizeof($lastDeals); $set2 < 4; $set2 ++) {
                            $dealval = [
                                "seatNo" => $set2,
                                "card_mutle" => 1,
                                "uid" => - 1,
                                "level" => - 1
                            ];
                            $dealval["cards"] = isset($Card2[$set2]) ? $Card2[$set2] : []; // 取牌数据
                            if (isset($dealval["cards"]) && ! empty($dealval["cards"])) {
                                MahjEnum::dealTwoEightLevel($dealval["cards"], $dealval["level"]); // 计算牌型
                            }
                            $lastDeals[] = $dealval; // 牌型数据就不要
                        }
                    }
                    BaseComm::distLastGameNoListRedis($allUserIds,TRUE,FALSE);      //清掉最新局号

                    //TODO 前端结算：
                    $pushdata = [ 'm' => BaseEnum::XYM_0X1C,  "data" => $lastDeals ];
                    AuthCommon::pushFrontWeb($playerFds, json_encode($pushdata)); 
                    
                    $tongjiRes = self::getCurrentLunGameTongji($gameNo) ;   //TODO 统计数据操作
                    
                    
                    //TODO 检查真实玩家用户掉线判断问题(如有玩家掉线 强制游戏结束 ====> GAME OVER )
                    $gamePdata = BaseComm::distMemoryRooms($gameNo, FALSE);
                    $userList = $gamePdata['lists'] ?? FALSE;
                    $gameIsStop = PlayCommon::gameUserIsFinish($gameId,$userList);
                    BaseEnum::Logger("[房间：{$gameRId}]下一局掉线检查： ( 是否强行结束 - isStop : {$gameIsStop}) -- 玩家：" . json_encode($userList), 'user-login');
                    BaseComm::gameLogger($gameNo,$gameLoggs,["finish"=>TRUE]);  //TODO 加入对局日志
                    //TODO 检查所有玩家提前结束游戏( 没有达到游戏房间的底注要求 ) 
                    if($lastNextGameStart === FALSE || $currentGameInng >= 2 || $gameIsStop === TRUE){
                        $pushdata2 = [ 'm' => BaseEnum::XYM_0X1E, "data" => $tongjiRes ];
                        AuthCommon::pushFrontWeb($playerFds, json_encode($pushdata2));                               // 推送不同的数据；
                        PlayCommon::gameNoticeSaveCache($gameId,$gameNo,$realUserNotices);                          //TODO 加入游戏通知列表
                        PlayCommon::doBackFreeRobot($gameId,$allUserIds);                                           //TODO 清掉所有机器人玩家被占用的缓存
                        //TODO 游戏提前结束
                        if($lastNextGameStart === FALSE || $gameIsStop === TRUE){
                            BaseEnum::Logger("牌九所有结算提前完成：所有轮游戏提前结束！！！！lastNextGameStart = {$lastNextGameStart} , gameIsDownStop = {$gameIsStop}；==>  " . print_r($lastDeals, true), 'user-login');
                            Timer::after(6800, function() use($playerFds){
                                $downpushRes = ['m'=>BaseEnum::XYM_FE0,"data"=>"","code"=>BaseEnum::MSG_0XE5,"level"=> 1,"msg"=> "玩家强制下线，游戏结束。"];
                                AuthCommon::pushFrontWeb($playerFds, json_encode($downpushRes));
                            });
                        }
                        return FALSE;   
                    }
                    // 开启下一局：30秒后再统计；
                    $nextTime = $this->getConfigTimeLong('nextgame_long') ?? 8; 
                    Timer::after($nextTime * 1000, function() use ($roomDetail, $gameRId, $gameNo,$playerFds){
                        $gameId = isset($roomDetail["gameId"]) ? intval($roomDetail["gameId"]) : 0;                     // ID
                        $this->WaitReStart($gameId, $gameRId, $gameNo,$playerFds);
                    });
                } else {
                    BaseEnum::Logger('牌九结算处理：结算失败 ！！！！PlayGrabNine-(asycWaitDealCenter)-；======>  ' . print_r($zjObject, true), 'user-login');
                    return FALSE;
                }
            }
        });
    }
    
    
    
    //TODO 统计数据操作
    protected static function getCurrentLunGameTongji($gameNo){
        if(!$gameNo) return FALSE;
        $tongjiRes = [];                //第二局的时间推出统计数据
        $gameNoLastKey = SysEnum::ONLINE_ROOMS_TONGJI_KEY.substr($gameNo, -10);
        $tongjidata = RedisTools::Pool_hGetAll($gameNoLastKey);                                       // TODO 前端结算汇总
        if(!empty($tongjidata) && sizeof($tongjidata)  > 0){
            foreach ($tongjidata as $onekey =>  $mystr){
                $ooj = !empty($mystr) ? json_decode($mystr,true) : false;
                if(!empty($ooj) && is_array($ooj) && sizeof($ooj) > 0) {
                    $tongjiRes[$onekey] = array_values($ooj);
                }
            }
            @ksort($tongjiRes);         //排序成正序操作
            $tongjiRes = array_values($tongjiRes);
        }
        return $tongjiRes;
    }
    
    
    
    
    /**
     * TODO 异步等待进入下一局
     * @param int $gameId
     * @param int $gameRId
     * @param string $gameNo
     */
    protected function WaitReStart($gameId, $gameRId, $gameNo,$playFds = FALSE ) {
        
        //TODO  10秒动画效果
        BaseEnum::Logger("等待进入下一局：PlayGrabNine-(WaitReStart)-----[$gameId,$gameRId,$gameNo]", 'user-login');
        if(!$playFds){
            $temps = BaseComm::distMemoryRooms($gameNo, FALSE);
            $playFds = (isset($temps['lists']) && !empty($temps['lists'])) ? array_column($temps['lists'], "fd", "seatNo") : false; // FD防变动
        }
        $Parms = [ "gameNo" => $gameNo, "clientIds" => $playFds, "status" => -1 ];
        $nextTime = $this->getConfigTimeLong('nextgamestart_long') ?? 3;
        $this->asycTimeStart($nextTime, $gameNo,$Parms, function () use ($gameNo, $gameId,$gameRId) {
            
            $fjDetail = BaseComm::distMemoryRooms($gameNo, FALSE);                      // 房间内的信息；
            if (isset($fjDetail["doReNext"]) && intval($fjDetail["doReNext"]) > 0) {    // 已经处理  === 查看当前状态是否启动；
                
                BaseEnum::Logger('等待进入下一局执行：PlayGrabNine-(WaitReStart)-[检查到doNext=1 已经执行处理过，不再处理]--->>>' . json_encode($fjDetail), 'user-login');
            } else {
                $fjDetail["doReNext"] = 1;
                BaseComm::distMemoryRooms($gameNo, TRUE, $fjDetail);                    // 保存标识不允许再次发起
                $allInning = isset($fjDetail['playNumber']) ? intval($fjDetail['playNumber']) : 1;      // 玩的局数
                $currentInning = isset($fjDetail['currInning']) ? intval($fjDetail['currInning']) : 0;  // 当前局
                $usrList = isset($fjDetail['lists']) ? $fjDetail['lists'] : FALSE;                      // 用户数据要清空值；
                if(!empty($usrList) && $allInning > $currentInning ){
                    foreach ($usrList as & $user) {
                        if (isset($user["uid"]) && intval($user["uid"]) > 0) {
                            $user['pstatus'] = 0;
                            $user['mainflag'] = 0;
                            $user['mutdotts'] = 0;
                            $user['maxQzMutle'] = 4;        // 最大倍数
                            $user['betmoney'] = 0;
                            $user['points'] = AuthCommon::getLoginUserPoints($user["uid"]);; // 玩家金额 > 房主的限额；
                        }
                    }
                    
                    //TODO 重新启动第二局游戏
                    $this->ReStartNewGame($gameRId, $gameNo, $usrList, $currentInning);
                }
            }
        });
    }
    
    
    
    

    /**
     * TODO 重启游戏  取回原来有效的数据； 生成新的房间信息 ，复制玩家信息 重新开局；
     * @param int $gameRId
     * @param string $gameNo
     * @param boolean $usrList
     * @param boolean $roomDetail
     */
    protected function ReStartNewGame($gameRId, $gameNo, $usrList = FALSE, $currentInning = 1)
    {
        $currentInning = ($currentInning < 1) ? 1 : $currentInning;
        $commSid = substr("{$gameNo}", - 10); // 取末尾的10位；
        $currentJu = intval($currentInning) + 1; // 本场局次：
        $lastChar = "{$currentInning}{$commSid}";

        // 房间信息
        $fjInfo = BaseComm::GameRoomDetail($gameRId);
        $gameId = isset($fjInfo['gameId']) ? intval($fjInfo['gameId']) : 0; // 多少局结束
        $playNum = isset($fjInfo['playNumber']) ? intval($fjInfo['playNumber']) : 1; // 多少局结束

        // 生成新的游戏号：
        $NewgameNo = BaseComm::getRoomUnioxNo($gameRId, $lastChar);
        $fjInfo["users_num"] = sizeof($usrList);
        $fjInfo["users"] = $usrList; // 游戏数据
        $allRecUids = !empty($usrList) ? array_unique(array_column($usrList, "agentId")) : FALSE;
        $fjInfo["agentsIds"] = $allRecUids ?? FALSE ;                               // 代理玩家ID列表
        
        //TODO 游戏启动入库
        $dbEx = GameCommon::createRoomsPlayinfo($gameRId, $NewgameNo, $fjInfo, 0);  // 房间落地写入DB并开始发牌
        BaseEnum::Logger('下一局开始：PlayGrabNine-(ReStartNewGame)-；======>  ' . print_r([ $gameRId, $gameNo, $NewgameNo, $dbEx ], true), 'user-login');
        if (! $dbEx) {
            BaseEnum::Logger("组建游戏新桌号：(createRoomsPlayinfo)-=={$NewgameNo} -- >>>重新启动游戏失败!!!" . json_encode($usrList), 'user-login');
            return FALSE;
        }

        //TODO 组装房间的状态信息；和玩家数据(fjStatus: 0 == 房间抢庄 )
        $fjPlays = array(
            "isOpen" => 1,
            "gameId" => $gameId,
            'grid' => $gameRId,
            "gameNo" => $NewgameNo,
            "fjStatus" => CardEnum::GAME_STEP_0,
            "currInning" => $currentJu,
            "playNumber" => $playNum, // 最多玩多少局才能结束；
            "create" => time(), // 创建时间
            "lists" => $usrList
        );
        BaseComm::distMemoryRooms($NewgameNo, TRUE, $fjPlays);  // 保存数据到内存
        BaseComm::distMemoryRooms($gameNo, TRUE, FALSE);        // 清空内存数据
        BaseComm::gameLogger($NewgameNo,"第{$currentJu}局正式开始");
        // 缓存玩家最近的游戏号；
        $allUids = array_column($usrList, "uid");
        BaseComm::distLastGameNoListRedis($allUids, TRUE,json_encode([$NewgameNo,$gameRId])); // 缓存玩家最近的房间号；
        // $redisobj->setRedisValue(SysEnum::ONLINE_ROOMS_LIST_KEY . $NewgameNo, json_encode($fjPlays), 7200); // 缓存游戏信息；
        BaseEnum::Logger("组建游戏新桌号：(joinGameRidUsers)-=={$NewgameNo} -- >>>" . json_encode($fjPlays), 'user-login');
        $gameResult = [
            "gstart" => 1,
            "gameNo" => $NewgameNo,
            "gameInning"=> $currentJu,
            "isFirst" => 0
        ];
        // 别外信息
        /*$roomLastPots = isset($fjInfo["smallPoints"]) ? floatval($fjInfo["smallPoints"]) : 0; // 房间的携带分要求；
        $roomEveryPots = isset($fjInfo["everyPoints"]) ? floatval($fjInfo["everyPoints"]) : 0; // 房间底分
         $gameResult["roomInfo"] = array(
            "gameId" => $gameId,
            "everyPoints" => $roomEveryPots,
            "smallPoints" => $roomLastPots,
            "curInning" => $currentJu,
            "total" => $playNum
        ); */  
        $players_fd = array_column($usrList, "fd", "seatNo");
        $push_data = [
            'm' => BaseEnum::XYM_0X16,
            "data" => $gameResult
        ];
        AuthCommon::pushFrontWeb($players_fd, json_encode($push_data)); // 推送数据到前端；游戏开始；

        // 互动开始
        $this->asycStartGame($NewgameNo, $gameRId, $fjInfo);
    }
}

?>